<template>
	<view class="content">
		<button type="default" @click="initialize">初始化</button>
		<button type="default" @click="searchBlue">搜索</button>
		<view class="list">
			<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
			<view v-for="(item, index) in dataList" :key="item.deviceId">
				<view>{{item.name}}</view>---
				<view>{{item.deviceId}}</view>---
				<view>{{item.RSSI}}</view>
				<button type="default" @click="lianjie(item.deviceId)" v-if="lanya.deviceId!==item.deviceId">连接</button>
				<button type="warn" @click="closelanya(item.deviceId)" v-if="lanya.deviceId==item.deviceId">断开</button>
			</view>
		</view>
		<button type="default" @click="communication">通讯</button>
		<h3>服务值</h3>
		<view class="list1">
			<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
			<view v-for="(item1, index1) in service" :key="item1.uuid">
				<view>{{item1.uuid}}</view>
				<view>是否主要{{item1.isPrimary}}</view>
				<button type="default" @click="choose(item1.uuid)" v-if="serviceuuid!==item1.uuid">选择</button>
				<button type="warn" @click="choose()" v-if="serviceuuid==item1.uuid">清空</button>
			</view>
		</view>
		<h3>特征值</h3>
		<view class="list1">
			<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
			<view v-for="(item1, index1) in characteristic" :key="item1.uuid">
				<view>{{item1.uuid}}</view>
				<view>可监听{{item1.properties.notify}}</view>
				<view>可写{{item1.properties.write}}</view>
				<view>可读{{item1.properties.read}}</view>
				<button type="default" @click="monitor(item1.uuid)"
					v-if="characteristicId!==item1.uuid&&item1.properties.notify==true">监听</button>
				<button type="default" v-if="item1.properties.read==true" @click="readData(item1.uuid)">读取数据</button>
				<button type="default" v-if="item1.properties.write==true" @click="open(item1.uuid)">写入数据</button>
			</view>
		</view>
		<view v-for="(item, index) in stringArr" :key="index"
			style="display:flex;padding: 10rpx;box-sizing: border-box;width: 100%;border-bottom: 1rpx solid #000;justify-content: center;">
			<text
				style="width:90%;display:inline-block;white-space: pre-wrap; word-wrap: break-word;height: auto;">{{item}}</text>
		</view>
		<u-popup :show="show" @close="close" mode="center">
			<view style="padding: 20rpx;">
				<view>{{characteristicId1}}</view>
				<input placeholder="请输入数据" border="surround" v-model="writeDataValue"></input>
				<button @click="writeData()">确定</button>
			</view>
		</u-popup>
	</view>
</template>
 
<script>
	export default {
		data() {
			return {
				dataList: [],
				lanya: {},
				service: [],
				characteristic: [],
				stringArr: [],
				serviceuuid: undefined,
				characteristicId: undefined,
				characteristicId1: undefined,
				show: false,
				writeDataValue: "BB9AA90CEE",
				dataObject: {}
			}
		},
		onLoad() {
			this.initialize();
		},
		methods: {
			open(e) {
				this.show = true;
				this.characteristicId1 = e;
				// console.log('open');
			},
			close() {
				this.show = false;
				this.characteristicId1 = undefined;
				// this.writeDataValue = undefined
				// console.log('close');
			},
			//初始化
			initialize() {
				uni.openBluetoothAdapter({
					// 蓝牙初始化成功执行
					success: (res) => {
						// 这里成功之后就不用管了,直接执行就行
						uni.showToast({
							title: "初始化成功!",
							icon: "none",
							duration: 2000,
						});
					},
					// 蓝牙初始化失败执行
					fail: (err) => {
						// 初始化失败之后有需求的话可以进行一些处理
						// 没有需求的也不用管
						// 一般情况下,还是需要分析一下原因的,这用用户和自己就知道为什么失败了
						if (err.errCode == 10001) {
							// 这种情况是未打开蓝牙 就需要提示用户检测蓝牙并打开
							uni.showToast({
								title: "请检查蓝牙是否开启!",
								icon: "none",
								duration: 2000,
							});
						}
						// 我这里只演示这一个,其他的状态可进入官网进行查看
						//  uni-app  https://uniapp.dcloud.io/api/system/bluetooth.html
						// 微信原生  https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html
					}
				})
			},
			//搜索蓝牙
			searchBlue() {
				uni.startBluetoothDevicesDiscovery({
					success: () => {
						// 调用成功之后就开始查询附近蓝牙了
						// 成功之后可调用这个函数,每次查询到新的蓝牙设备就会调用
						// 这个函数使用不使用都可以,不影响查询的结果
						uni.onBluetoothDeviceFound((devices) => {
							console.log('蓝牙', devices) // 蓝牙设备信息
							// 返回的数据是ArrayBuffer格式的需要进行转换,不然咱也看不懂都是些啥
							// ArrayBuffer 的转换后面会详细写出来
						})
 
						setTimeout(() => {
							uni.getBluetoothDevices({
								success: (res) => {
									// res.devices 为 查询到的蓝牙设备列表
									// 拿到结果之后,进行处理
									// 首先进行过滤一下查询的列表,会出现很多未知设备这类的,这里就直接把它们给排除掉了,只留下有完整名称的设备
									var arr = []
									res.devices.forEach((element) => {
										if (element.name !== "位置设备") {
											arr.push(element);
										}
									})
									// 筛选之后在进行判断是否有正常的蓝牙设备  
									// 当然你也可以查看一下被筛选掉的设备是否有你所使用的设备,如果有你可以去掉筛选 或者自己定义筛选条件
									if (arr.length == 0) {
										uni.showToast({
											title: "未查询到可用设备,请重新扫描",
											duration: 1000,
											icon: "none",
										});
									}
									// 最后这个arr就是所查到的列表
									let arr1 = []
									arr.map((i) => {
										if (i.name) {
											arr1.push(i);
										}
									})
									this.dataList = arr1
									// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
									// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
									// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
								}
							})
						}, 2000)
					}
				})
			},
			// 关闭蓝牙搜索  
			stopBluetoothDevicesDiscovery() {
				uni.stopBluetoothDevicesDiscovery({
					success: e => {
						console.log('停止搜索蓝牙设备:' + e);
					},
					fail: e => {
						console.log('停止搜索蓝牙设备失败，错误码：' + e);
					}
				});
			},
			//连接蓝牙
			lianjie(e) {
				console.log('连接', e);
				// 为了能否顺利的连接蓝牙 可以先查询一下是否有设备已经连接蓝牙了
				// 手环这一类的设备 可能会对程序造成干扰 会一直显示设备已经连接
				uni.getConnectedBluetoothDevices({
					success: (res) => {
						// 因为我为了蓝牙的连接的稳定性,就做了这一步
						// 大家使用的过程中可以省略这一步直接进行蓝牙设备
						// 但是不确定是否可以正常进行蓝牙连接, 大家可以尝试一下
						// 如果返回的列表不等于空,就代表已经有设备连接
						if (res.devices.length !== 0) {
							// 这里就需要提示用户蓝牙已连接
							uni.showModal({
								title: "提示!",
								content: "当前蓝牙已于id为" + res.devices[0].deviceId + "的设备连接,是否断开连接",
								success: (row) => {
									// 用户点击确定执行
									if (row.confirm) {
										// 用户点击确定之后把当前连接的蓝牙断开
										uni.closeBLEConnection({
											// 一定要传入当前连接蓝牙的deviceId
											deviceId: res.devices[0].deviceId,
											success: () => {
												// 到这里就已经断开成功了,再次点击就可以进行连接了
												uni.showToast({
													title: "连接已断开!",
													icon: "none",
													duration: 2000,
												});
												this.lanya = {}
												this.service = []
												this.characteristic = []
												this.serviceuuid = undefined;
												this.characteristicId = undefined;
												this.stringArr = []
											},
											fail: (err) => {
												// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
												uni.showToast({
													title: err.errMsg,
													icon: "none",
													duration: 2000,
												});
											}
										})
									} else {
										// 用户取消之后不需要做任何操作
										console.log('用户点击了取消')
									}
								}
							});
						} else {
							// 当前未处于已连接状态就开始进行连接,没有连接的情况下就可以直接连接蓝牙
							uni.createBLEConnection({
								// 连接的时候一定传入要连接蓝牙的deviceId
								deviceId: e,
								// 这里可以选择设置一个延迟,如果延迟时间过了没有连接成功就直接提示报错
								timeout: 5000,
								success: (res) => {
									// 连接成功之后可以再次进行查询一下当前连接的蓝牙信息,保存下载,后面方便使用
									uni.getConnectedBluetoothDevices({
										success: (devicess) => {
											// devicess就是当前已经连接的蓝牙列表
											// 在这判断一下有没有蓝牙,如果有就证明确实已经连接成功
											if (devicess.devices[0]) {
												// 到这里就可以提示用户成功,并且吧蓝牙信息保存下来方便后面使用
												console.log('连接成功', devicess.devices[
													0]) // 蓝牙信息
												uni.showToast({
													title: "连接成功!",
													icon: "none",
													duration: 2000,
												});
												this.lanya = devicess.devices[
													0]
												this.stopBluetoothDevicesDiscovery();
											} else {
												// 如果走这里面就是没有蓝牙设备,就要查看一下,看看是否真的连接成功了	
											}
										}
									})
								},
								fail: (err) => {
									// 在这里查看连接失败的信息,判断为什么连接失败
									console.log(err)
								}
							})
						}
					}
				})
 
			},
			//断开蓝牙
			closelanya() {
				uni.closeBLEConnection({
					// 一定要传入当前连接蓝牙的deviceId
					deviceId: this.lanya.deviceId,
					success: () => {
						// 到这里就已经断开成功了,再次点击就可以进行连接了
						uni.showToast({
							title: "连接已断开!",
							icon: "none",
							duration: 2000,
						});
						this.lanya = {}
						this.service = []
						this.characteristic = []
						this.serviceuuid = undefined;
						this.characteristicId = undefined;
						this.stringArr = []
					},
					fail: (err) => {
						// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
						uni.showToast({
							title: err.errMsg,
							icon: "none",
							duration: 2000,
						});
					}
				})
			},
			//服务值获取
			communication() {
				// 蓝牙接收数据主要使用的api是开启监听(uni.notifyBLECharacteristicValueChange)
				// 但是开启监听是需要几个特殊的值才能开启
				// 所以开启之前我们需要获取这个值 
				// deviceId  蓝牙deviceId,蓝牙信息中包含的有
				// serviceId 蓝牙服务值,根据蓝牙deviceId获取
				// characteristicId 蓝牙特征值 根据serviceId 获取
				// 首先根据deviceId  获取到服务值 serviceId 
				uni.getBLEDeviceServices({
					deviceId: this.lanya.deviceId, // 获取服务值,需要传入deviceId
					success: (res) => {
						// res.services 返回一个数组,里面包含着支持不同的通讯方式的serviceId  一般为三个左右,也有可能更多
						console.log('服务值', res.services)
						this.service = res.services
						// 拿到之后根据自己所需要的去保存serviceId,在后面使用
						// 这里建议多试试,说不定那个可以用,又或者某个不能用
						if (res.services.length <= 0) {
							this.communication();
						} else {
							// this.choose(res.services[res.services.length - 2].uuid);
							// this.choose(res.services[0].uuid);
						}
					},
					fial: (err) => {
						// 一般来说只要	deviceId 对,就不会报错
					}
				})
			},
			//选择服务值
			choose(e) {
				this.characteristic = []
				this.stringArr = []
				this.characteristicId = undefined
				this.serviceuuid = e;
				this.characteristicget();
			},
			//特征值获取
			characteristicget() {
				//选第4个
				// 获取到之后就可以去拿着获取到的serviceId和deviceId去获取特征值
				uni.getBLEDeviceCharacteristics({
					deviceId: this.lanya.deviceId, // 传入获取到的deviceId
					serviceId: this.serviceuuid, // 传入获取到的serviceuuid
					success: (ress) => {
						// ress里面就是获取到的蓝牙特征值
						// 注意:根据传入serviceuuid的不同获取到的特征值也是不一样的,
						// 特征值分为,可读、可写、可通知等几个类型的，根据自己想要的操作选择特征值
						console.log('特征值', ress)
						this.characteristic = ress.characteristics
						// for (var i = 0; i < ress.characteristics.length; i++) {
						// 	var model = ress.characteristics[i];
 
						// 	if (model.properties.notify == true) {
						// 		this.monitor(model.uuid);
						// 	}
						// }
					},
					fial: (err) => {
						// 一般来说只要参数对,就不会报错
					}
				})
			},
			//监听
			monitor(e) {
				//选第1个
				// 这里所声明介绍一下所用到的东西
				// deviceId 就是上面蓝牙设备的deviceId
				// serviceuuid 就是上面根据蓝牙设备获取到的serviceuuid 
				// characteristics 就是上面根据 deviceId 和 serviceuuid 获取到的
				// characteristics 是一个数组里面包含着多个特征值, 根据使用去拿响应的特征值
 
				// 我这里没有直接使用,而是进行一个循环判断,判断这么多的特征值中那个是符合要求的
				// var characteristicId;
				// var i = 0
				// then.characteristiclist.forEach((element) => {
				// 	if (element.properties.notify == true) {
				// 		if (i == 0) {
				// 			characteristicId = element.uuid;
				// 			i++;
				// 		}
				// 	}
				// });
				// 为什么循环
				// 因为开启uni.notifyBLECharacteristicValueChange需要特征值是需要固定的,我就直接判断写入了
				let that = this
				uni.notifyBLECharacteristicValueChange({
					deviceId: that.lanya.deviceId,
					serviceId: that.serviceuuid,
					characteristicId: e,
					state: true,
					success: () => {
						that.characteristicId = e
						console.log('监听启动成功');
						this.stringArr = []
						// 启用成功之后就可以在uni.onBLECharacteristicValueChange 中获取到蓝牙设备发送的数据
						that.rxd()
					},
				});
 
			},
			//数据接受
			rxd() {
				console.log('监听返回');
				let that = this
				let str1 = '';
				uni.onBLECharacteristicValueChange((res) => {
					console.log('接收数据', res.value)
					// 这就是蓝牙发送的数据 但是现在的数据,还不能直观的看出来是什么,还需要进行一些列转换才能直观查看
					// ArrayBufer转16进制
					console.log('ArrayBufer转16进制', that.buf2hex(res.value))
					let str = that.buf2hex(res.value);
					console.log(str);
 
					//16进制转字符串
					// console.log('ArrayBufer转字符串',that.hexToString(that.buf2hex(res.value)))
					// if (that.hexToString(that.buf2hex(res.value))) {
					// 	that.stringArr.push(that.hexToString(that.buf2hex(res.value)))
					// }
					//16进制转字符串处理中文乱码
					// console.log('ArrayBufer转字符串处理乱码',that.utf8to16(that.hexToString(that.buf2hex(res.value))))
					// if (that.utf8to16(that.hexToString(that.buf2hex(res.value)))) {
					// 	that.stringArr.push(that.utf8to16(that.hexToString(that.buf2hex(res.value))))
					// }
 
					//ArrayBufer直接转字符串
					// console.log('ArrayBufer直接转字符串',that.utf8to16(that.buf2str(res.value)));
					// if (that.utf8to16(that.buf2str(res.value))) {
					// 	that.stringArr.push(that.utf8to16(that.buf2str(res.value)))
					// }
					// 经过 this.buf2str 转换之后就可以直观查看 蓝牙返回的信息
				});
 
			},
			// 读取数据
			readData(e) {
				//选第3个
				let that = this
				uni.readBLECharacteristicValue({
					deviceId: that.lanya.deviceId,
					serviceId: that.serviceuuid,
					characteristicId: e,
					success(res) {
						console.log('读取数据:', res.errCode)
					}
				})
			},
			//写入数据
			writeData() {
				//选第2个
				// 所用到的就是上面获取到的
				// 但是特征值跟上面不一样,需要支持可写
				// var characteristicId;
				// var i = 0;
				// then.characteristiclist.forEach((element) => {
				// 	if (element.properties.write == true) {
				// 		if (i == 0) {
				// 			characteristicId = element.uuid;
				// 			i++;
				// 		}
				// 	}
				// });	
				let that = this
				// console.log(that.writeDataValue);
				// console.log(that.hexToString(that.writeDataValue));
				// console.log(that.strToHexCharCode(that.writeDataValue));
				// console.log(that.hex2buf(that.strToHexCharCode(that.writeDataValue)));
				console.log('12进制转arraybuffer', that.hex2buf(that.writeDataValue));
				uni.writeBLECharacteristicValue({
					deviceId: that.lanya.deviceId,
					serviceId: that.serviceuuid,
					characteristicId: that.characteristicId1,
					value: that.hex2buf(that.writeDataValue),
					// value: that.hex2buf(that.strToHexCharCode(that.writeDataValue)),
					// writeType: "write",
					success: (res) => {
						console.log('指令写入成功');
						console.log(res);
						that.show = false
						that.characteristicId1 = undefined;
						// that.writeDataValue = undefined
						uni.showToast({
							title: "指令写入成功",
							icon: "none",
							duration: 2000,
						});
						// 写入成功之后在onBLECharacteristicValueChange里面应该是有反馈的
						that.rxd()
					},
					fail(err) {
						console.log(err);
						uni.showToast({
							title: err.message,
							icon: "none",
							duration: 2000,
						});
					}
				})
				// this.string2buffer 把16进制字符串转换成buffer之后进行写入,我也忘了直接传入字符串可不可以使用,应该是不行,可以尝试一下,不行就按着我的来也行
 
			},
			//蓝牙状态变化处理
			statesChange() {
				uni.onBLEConnectionStateChange(res => {
					// 该方法回调中可以用于处理连接意外断开等异常情况
					console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`);
 
				});
			},
			// arraybuffer类型转16进制字符串
			buf2hex(buffer) {
 
				const hexArr = Array.prototype.map.call(
					new Uint8Array(buffer),
					function(bit) {
						return ('00' + bit.toString(16)).slice(-2)
					}
				)
				console.log('arraybuffer类型转16进制', hexArr.join(''));
				return hexArr.join('')
 
			},
			// 16进制转arraybuffer类型
			hex2buf(e) {
				var hex = e
				var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function(h) {
					return parseInt(h, 16)
				}))
				var buffer = typedArray.buffer
				return buffer;
			},
			// 16进制转字符串
			hexToString(hexCharCodeStr) {
				var trimedStr = hexCharCodeStr.trim();
				var rawStr =
					trimedStr.substr(0, 2).toLowerCase() === "0x" ?
					trimedStr.substr(2) :
					trimedStr;
				var len = rawStr.length;
				if (len % 2 !== 0) {
					console.log("非法格式ASCII码!");
					return "";
				}
				var curCharCode;
				var resultStr = [];
				for (var i = 0; i < len; i = i + 2) {
					curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value
					resultStr.push(String.fromCharCode(curCharCode));
				}
				return resultStr.join("");
			},
			//字符串转16进制
			strToHexCharCode(str) {
				if (str === "")
					return "";
				var hexCharCode = [];
				hexCharCode.push("0x");
				for (var i = 0; i < str.length; i++) {
					hexCharCode.push((str.charCodeAt(i)).toString(16));
				}
				return hexCharCode.join("");
			},
			//处理中文乱码问题
			utf8to16(str) {
				// console.log(str);
				var out, i, len, c;
				var char2, char3;
				out = "";
				len = str.length;
				i = 0;
				while (i < len) {
					c = str.charCodeAt(i++);
					switch (c >> 4) {
						case 0:
						case 1:
						case 2:
						case 3:
						case 4:
						case 5:
						case 6:
						case 7:
							out += str.charAt(i - 1);
							break;
						case 12:
						case 13:
							char2 = str.charCodeAt(i++);
							out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
							break;
						case 14:
							char2 = str.charCodeAt(i++);
							char3 = str.charCodeAt(i++);
							out += String.fromCharCode(((c & 0x0F) << 12) |
								((char2 & 0x3F) << 6) |
								((char3 & 0x3F) << 0));
							break;
					}
				}
 
				console.log(out, 'out')
				return out;
			},
		},
	}
</script>
 
<style scoped lang="scss">
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}
 
	.list {
		padding: 20rpx;
 
		view {
			display: flex;
			justify-content: space-around;
		}
	}
</style>